struct RefCount {
    var allocator: Box[Allocator];
    var count: Int = 1;
}

/**
 * A smart pointer to type T that employs reference counting. Call `ref()` to
 * obtain an additional reference, and `release()` when the reference is
 * released; the memory will be reclaimed when there are no more active
 * references.
 */
abstract Shared[T]: Ptr[T] {
    // public static function new(allocator: Box[Allocator]): Self {
    //     return Self.alloc(sizeof T) as Shared[T];
    // }

    public static function alloc(allocator: Box[Allocator], bytes: Size): Self {
        var ptr: Ptr[RefCount] = allocator.calloc(sizeof RefCount + bytes);
        ptr.count = 1;
        ptr.allocator = allocator;
        return (ptr + 1) as Self;
    }

    /**
     * Increment the reference count and return the pointer.
     */
    public function ref(): Self {
        var meta = this.metadata();
        ++meta.count;
        return this;
    }

    /**
     * Decrement the reference count. If this causes the count to drop to 0,
     * free it.
     */
    public function release(): Bool {
        var meta = this.metadata();
        if meta.count == 0 {
            return true;
        } else if --meta.count == 0 {
            this.free();
            return true;
        } else {
            return false;
        }
    }

    /**
     * Decrement the reference count, but don't free. If this function returns
     * true, the user is responsible to free.
     */
    public function decrement(): Bool {
        var meta = this.metadata();
        if meta.count == 0 {
            return false;
        } else if --meta.count == 0 {
            return true;
        } else {
            return false;
        }
    }

    public function free() {
        var meta = this.metadata();
        meta.allocator.free(meta);
    }

    public function active(): Bool {
        var meta = this.metadata();
        return meta.count > 0;
    }

    public function rc(): Size {
        var meta = this.metadata();
        return meta.count;
    }

    function metadata(): Ptr[RefCount] {
        return (this as Ptr[Void] as Ptr[RefCount]) - 1;
    }
}
